/*    Copyright 2010 Tobias Marschall
 *
 *    This file is part of MoSDi.
 *
 *    MoSDi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    MoSDi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with MoSDi.  If not, see <http://www.gnu.org/licenses/>.
 */

package mosdi.paa;

import java.util.Arrays;

import mosdi.fa.Partition;
import mosdi.util.ProductEncoder;

/** DAA that emulates two underlying DAAs and computes the difference of 
 *  these DAAs. */
public class EmissionDifferenceDAA extends MinimizableDAA {

	private DAA daa0;
	private DAA daa1;
	private ProductEncoder stateEncoder;
	private int maxDifference;
	public final static int INVALID_VALUE = Integer.MIN_VALUE; 
	
	public EmissionDifferenceDAA(DAA daa0, DAA daa1, int maxDifference) {
		if (daa0.getAlphabetSize()!=daa1.getAlphabetSize()) {
			throw new IllegalArgumentException("Alphabet size mismatch.");
		}
		this.daa0 = daa0;
		this.daa1 = daa1;
		this.maxDifference = maxDifference;
		this.stateEncoder = new ProductEncoder(daa0.getStateCount(), daa1.getStateCount());
	}

	@Override
	public int getAlphabetSize() {
		return daa0.getAlphabetSize();
	}

	@Override
	public int getEmission(int state) {
		return 0;
	}

	@Override
	public int getEmissionCount() {
		return 0;
	}

	@Override
	public int getStartState() {
		return stateEncoder.encode(daa0.getStartState(), daa1.getStartState());
	}

	public int encodeValue(int value) {
		if (value==INVALID_VALUE) return 0;
		if ((value<-maxDifference) || (value>maxDifference)) throw new IllegalArgumentException();
		return value + maxDifference + 1;
	}

	/** Decodes a (unsigned) DAA value into a (signed) difference. The return
	 *  value INVALID_VALUE indicates that the difference grew larger than
	 *  maxDifference in the course of the computation and thus, the true
	 *  difference is unknown. 
	 */
	public int decodeValue(int encodedValue) {
		if (encodedValue==0) return INVALID_VALUE;
		int value = encodedValue - maxDifference - 1;
		if ((value<-maxDifference) || (value>maxDifference)) throw new IllegalArgumentException();
		return value;
	}

	@Override
	public int getStartValue() {
		return encodeValue(0);
	}

	@Override
	public int getStateCount() {
		return stateEncoder.getValueCount();
	}

	@Override
	public int getTransitionTarget(int state, int character) {
		int[] decodedState = stateEncoder.decode(state);
		decodedState[0] = daa0.getTransitionTarget(decodedState[0], character);
		decodedState[1] = daa1.getTransitionTarget(decodedState[1], character);
		return stateEncoder.encode(decodedState);
	}

	@Override
	public int getValueCount() {
		// -maxDifference,...,0,...,maxDifference plus extra space for one
		// value indicating an invalid result
		return 2*maxDifference + 1 + 1;
	}

	@Override
	public int performOperation(int state, int value, int emission) {
		int decodedValue = decodeValue(value);
		if (decodedValue==INVALID_VALUE) return encodeValue(INVALID_VALUE);
		int[] decodedState = stateEncoder.decode(state);
		decodedValue += daa0.getEmission(decodedState[0]) - daa1.getEmission(decodedState[1]);
		if ((decodedValue<-maxDifference) || (decodedValue>maxDifference)) return encodeValue(INVALID_VALUE);
		return encodeValue(decodedValue);
	}

	@Override
	public Partition getStatePartition() {
		Integer[] emissionDifferences = new Integer[getStateCount()];
		for (int state=0; state<getStateCount(); ++state) {
			int[] decodedState = stateEncoder.decode(state);
			emissionDifferences[state] = daa0.getEmission(decodedState[0]) - daa1.getEmission(decodedState[1]);
		}
		return new Partition(Arrays.asList(emissionDifferences));
	}

}
